春天是一個我搞笑的說法,是指我大Java後端幾乎不可能不學的 Spring Framework。
其實這也是另外一個 Java 菜鳥常問的問題,請問 Spring Framework 怎麼學?
施主,J葛問題要問你自己 (毆飛)
好啦其實羅馬不是一天造成的,以前春天真的沒有這麼龐大。
一開始他只是個實作 DI (Dependency Injection), IOC (Inversion of Control), AOP (Aspect-Oriented Programming) 的東東。
就是在辣個被 EJB 荼毒的年代 (講的好像我 EJB 多熟,我根本沒寫幾次,哈哈),出來解救萬民于水火之中的救星。
結果今天自己好像長著長著又變另外一個 EJB,噗,別誤解,我不是春天黑R。
(Rod Johnson 從背後飛踢我)
那這邊我們就來講講以前是怎樣,春天又是怎麼好棒棒的好了 (極力挽回)。
其實也沒有多咖炸,只是來展示一下如果不用 Spring,你的 Java Web 後端又要長成 MVC & 3 tier architecture 那個長相,會怎麼寫。
假設你的 Model 是訂單好了,那麼你通常會有一個 Order Servlet,一個處理相關業務邏輯的 Order BO (Business Object),配上一個處理相關 CRUD 的 Order DAO (Data Access Object)。並且 BO 和 DAO 都只是介面,還會有它的實作類,OrderBoImpl 和 OrderDaoImpl。
再來來看 Servlet 是 per request per thread,並且是同一個 url 的請求對到同一個 Servlet 實例。所以 Servlet 其實有 Thread Safe 問題,使用上要注意。在這裡我們先不探討對於類別實例化管理相依的議題,也就是到底是要 Singleton 還是 Prototype?生出來的實例又該由誰控管?
你的 OrderServlet 可能會長這樣。
@WebServlet(name="order", urlPatterns={"/bet/v1/order"})
public class OrderServlet extends HttpServlet {
private OrderBo orderBo = new OrderBoImpl();
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// some common action
// some logic
}
}
你的 OrderBoImpl 可能會長這樣。
public class OrderBoImpl extends OrderBo {
private OrderDao orderDao = new OrderDaoImpl();
public List<OrderEntity> query(OrderRequest orderReq) {
// some common action
// some logic
}
}
好,傑出的一手,看起來很好阿,是要改啥? (大誤)
我想至少你應該會看出一個問題,OrderBo 在 OrderServlet new 出來,OrderDao 在 OrderBoImpl new 出來,這個是很明顯的相依。
再來第二個問題是,雖然有介面,但是還是直接用 hard code 的方式直接 new 實例出來,這也不是一個好的方式。
第三個就是你在每一個 method 裡頭可能都會有一些重複的邏輯,例如日誌、計時和驗證。
要輕鬆解決這些問題也不難,透過 Reflection 在搭配 Config 就可以靈活處理,其實 Spring 也是這樣做。
重複的邏輯最粗糙的作法可以透過抽取成 Util 類別處理掉,這個 Spring 是實作 AOP 做掉。
只是 Spring 更強的是,它的核心就已經混著一堆高級設計,這個就留待各位自行細細品嚐了XD (飄走)
DI (Dependency Injection) & IOC (Inversion of Control),姿勢相同角度不同的兩個東西。
首先來看 Spring 最基本的那個 Annotation,@Component,還可以向下細分成 @Controller、@Service、@Repository。
這個其實就是對應剛剛我們那些元件,OrderServlet、OrderBo、OrderDao。
Spring 預設對於元件的管理是 Singleton (單例),但你可以把它調成 Prototype (叫到就生新的),甚至在 Spring MVC 裡頭的 @Controller 還能調整成 by Request or Session。也就是說這些元件的生命週期不再是硬生生的 hard code 在程式裡,而是由 Spring 幫你管理。
你變得不再寫出實作類,最經典的 Spring Style 像下面這樣。
@Service
public class OrderBoImpl extends OrderBo {
@Autowired
private OrderDao orderDao;
public List<OrderEntity> query(OrderRequest orderReq) {
// some common action
// some logic
}
}
注意到上面多個 @Service 代表他是 Spring 管理的元件 (寫成 @Component 也可,但分開寫更可讀)。
OrderDao 沒有宣告實作類,上面多了一個 @Autowired 代表 Spring 會幫他注入實作類。
當然你也必須在 .xml 或是 .java 裡頭定義元件的一些資訊,讓 Spring 幫你在生成並管理實例,最後在適當的時間注入 (DI),然後你的依賴就不是在本來的單線由上至下 (Servlet -> BO -> DAO),而是透過外部 Spring 來管理,依賴就這樣被反轉了 (IOC)。
講完了上面,那 AOP 又是啥?回頭想想如果我們把日誌記錄寫成 Util 類別,那我們一定就是要在特定的時機呼叫他然後寫下日誌。這個日誌需求的邏輯專業術語稱為 Aspect,實作類稱為 Advice,特定的時機稱為 Joinpoint,最後有一個描述這些東西的叫做 Pointcut。
所以假如說你是要在進入某一個 method 後立刻 logging 記錄參數,完成邏輯結束之後 logging 結果。舉例來說,你在 Pointcut 定義一個 LoggingAdvice 然後 Joinpoint 是 Around Method。這個把業務流程切成多個時機點並試著想要縫合或者說插入一些東西的設計就稱為剖面導向設計 (AOP)。
也許你還是有點迷惘,沒關係,我當年也很迷惘 (燦笑)。套句某位前輩說的話,當年他同時學 Maven、Gradle、Spring 超挫折。不過我是同時學前兩個就超挫折了,哈哈。
多實作多看,新的不知為何而用就看看就的寫法也許你就會懂為什麼這樣做比較好或是為什麼這樣做了,共勉之!有問題也歡迎發問或聯絡我。
About Me
Jian-Min Huang
wide range skill set backend engineer
Research, Architecture, Coding, DB, Ops, Infra.
mainly write Java but also ❤️ Scala, Kotlin and Go